<template>
    <view class="wd-month-panel">
        <view v-if="showPanelTitle" class="wd-month-panel__title">
            {{ title }}
        </view>
        <view class="wd-month-panel__weeks">
            <view v-for="item in 7" :key="item" class="wd-month-panel__week">
                {{ weekLabel(item + firstDayOfWeek) }}
            </view>
        </view>
        <scroll-view
            :class="`wd-month-panel__container ${!!timeType ? 'wd-month-panel__container--time' : ''}`"
            :style="`height: ${scrollHeight}px`"
            scroll-y
            @scroll="monthScroll"
            :scroll-top="scrollTop"
        >
            <view
                v-for="(item, index) in months"
                :key="index"
                :id="`month${index}`"
            >
                <month
                    :type="type"
                    :date="item.date"
                    :value="value"
                    :min-date="minDate"
                    :max-date="maxDate"
                    :first-day-of-week="firstDayOfWeek"
                    :formatter="formatter"
                    :max-range="maxRange"
                    :range-prompt="rangePrompt"
                    :allow-same-day="allowSameDay"
                    :default-time="defaultTime"
                    :showTitle="index !== 0"
                    @change="handleDateChange"
                />
            </view>
        </scroll-view>
        <view v-if="timeType" class="wd-month-panel__time">
            <view
                v-if="type === 'datetimerange'"
                class="wd-month-panel__time-label"
            >
                <view class="wd-month-panel__time-text">
                    {{
                        timeType === 'start'
                            ? translate('startTime')
                            : translate('endTime')
                    }}
                </view>
            </view>
            <view class="wd-month-panel__time-picker">
                <wd-picker-view
                    v-if="timeData.length"
                    v-model="timeValue"
                    :columns="timeData"
                    :columns-height="125"
                    :immediate-change="immediateChange"
                    @change="handleTimeChange"
                    @pickstart="handlePickStart"
                    @pickend="handlePickEnd"
                />
            </view>
        </view>
    </view>
</template>

<script lang="ts">
export default {
    options: {
        addGlobalClass: true,
        virtualHost: true,
        styleIsolation: 'shared',
    },
}
</script>

<script lang="ts" setup>
import wdPickerView from '../../wd-picker-view/wd-picker-view.vue'
import { computed, ref, watch, onMounted } from 'vue'
import { debounce, isArray, isEqual, isNumber, pause } from '../../common/util'
import {
    compareMonth,
    formatMonthTitle,
    getMonthEndDay,
    getMonths,
    getTimeData,
    getWeekLabel,
} from '../utils'
import Month from '../month/month.vue'
import {
    monthPanelProps,
    type MonthInfo,
    type MonthPanelTimeType,
    type MonthPanelExpose,
} from './types'
import { useTranslate } from '../../composables/useTranslate'
import type { CalendarItem } from '../types'

const props = defineProps(monthPanelProps)
const emit = defineEmits(['change', 'pickstart', 'pickend'])

const { translate } = useTranslate('calendar-view')

const scrollTop = ref<number>(0) // 滚动位置
const scrollIndex = ref<number>(0) // 当前显示的月份索引
const timeValue = ref<number[]>([]) // 当前选中的时分秒

const timeType = ref<MonthPanelTimeType>('') // 当前时间类型，是开始还是结束
const innerValue = ref<string | number | (number | null)[]>('') // 内部保存一个值，用于判断新老值，避免监听器触发

const handleChange = debounce((value) => {
    emit('change', {
        value,
    })
}, 50)

// 时间picker的列数据
const timeData = computed<Array<CalendarItem[]>>(() => {
    let timeColumns: Array<CalendarItem[]> = []
    if (props.type === 'datetime' && isNumber(props.value)) {
        const date = new Date(props.value)
        date.setHours(timeValue.value[0])
        date.setMinutes(timeValue.value[1])
        date.setSeconds(props.hideSecond ? 0 : timeValue.value[2])
        const dateTime = date.getTime()
        timeColumns = getTime(dateTime) || []
    } else if (isArray(props.value) && props.type === 'datetimerange') {
        const [start, end] = props.value!
        const dataValue = timeType.value === 'start' ? start : end
        const date = new Date(dataValue || '')
        date.setHours(timeValue.value[0])
        date.setMinutes(timeValue.value[1])
        date.setSeconds(props.hideSecond ? 0 : timeValue.value[2])
        const dateTime = date.getTime()
        const finalValue = [start, end]
        if (timeType.value === 'start') {
            finalValue[0] = dateTime
        } else {
            finalValue[1] = dateTime
        }
        timeColumns = getTime(finalValue, timeType.value) || []
    }
    return timeColumns
})

// 标题
const title = computed(() => {
    return formatMonthTitle(months.value[scrollIndex.value].date)
})

// 周标题
const weekLabel = computed(() => {
    return (index: number) => {
        return getWeekLabel(index - 1)
    }
})

// 滚动区域的高度
const scrollHeight = computed(() => {
    const scrollHeight: number = timeType.value
        ? props.panelHeight - 125
        : props.panelHeight
    return scrollHeight
})

// 月份日期和月份高度
const months = computed<MonthInfo[]>(() => {
    return getMonths(props.minDate, props.maxDate).map((month, index) => {
        const offset = (7 + new Date(month).getDay() - props.firstDayOfWeek) % 7
        const totalDay = getMonthEndDay(
            new Date(month).getFullYear(),
            new Date(month).getMonth() + 1,
        )
        const rows = Math.ceil((offset + totalDay) / 7)
        return {
            height: rows * 64 + (rows - 1) * 4 + (index === 0 ? 0 : 45), // 每行64px高度,除最后一行外每行加4px margin,加上标题45px
            date: month,
        }
    })
})

watch(
    () => props.type,
    (val) => {
        if (
            (val === 'datetime' && props.value) ||
            (val === 'datetimerange' &&
                isArray(props.value) &&
                props.value &&
                props.value.length > 0 &&
                props.value[0])
        ) {
            setTime(props.value, 'start')
        }
    },
    {
        deep: true,
        immediate: true,
    },
)

watch(
    () => props.value,
    (val) => {
        if (isEqual(val, innerValue.value)) return

        if (
            (props.type === 'datetime' && val) ||
            (props.type === 'datetimerange' &&
                val &&
                isArray(val) &&
                val.length > 0 &&
                val[0])
        ) {
            setTime(val, 'start')
        }
    },
    {
        deep: true,
        immediate: true,
    },
)

onMounted(() => {
    scrollIntoView()
})

/**
 * 使当前日期或者选中日期滚动到可视区域
 */
async function scrollIntoView() {
    // 等待渲染完毕
    await pause()
    let activeDate: number | null = 0
    if (isArray(props.value)) {
        // 对数组按时间排序,取第一个值
        const sortedValue = [...props.value].sort((a, b) => (a || 0) - (b || 0))
        activeDate = sortedValue[0]
    } else if (isNumber(props.value)) {
        activeDate = props.value
    }

    if (!activeDate) {
        activeDate = Date.now()
    }

    let top: number = 0
    let activeMonthIndex = -1
    for (let index = 0; index < months.value.length; index++) {
        if (compareMonth(months.value[index].date, activeDate) === 0) {
            activeMonthIndex = index
            // 找到选中月份后,计算选中日期在月份中的位置
            const date = new Date(activeDate)
            const day = date.getDate()
            const firstDay = new Date(date.getFullYear(), date.getMonth(), 1)
            const offset = (7 + firstDay.getDay() - props.firstDayOfWeek) % 7
            const row = Math.floor((offset + day - 1) / 7)
            // 每行高度64px,每行加4px margin
            top += row * 64 + row * 4
            break
        }
        top += months.value[index] ? Number(months.value[index].height) : 0
    }
    scrollTop.value = 0
    if (top > 0) {
        await pause()
        // 如果不是第一个月才加45
        scrollTop.value = top + (activeMonthIndex > 0 ? 45 : 0)
    }
}
/**
 * 获取时间 picker 的数据
 * @param {timestamp|array} value 当前时间
 * @param {string} type 类型，是开始还是结束
 */
function getTime(value: number | (number | null)[], type?: string) {
    if (props.type === 'datetime') {
        return getTimeData({
            date: value as number,
            minDate: props.minDate,
            maxDate: props.maxDate,
            filter: props.timeFilter,
            isHideSecond: props.hideSecond,
        })
    } else {
        if (type === 'start' && isArray(props.value)) {
            return getTimeData({
                date: (value as Array<number>)[0],
                minDate: props.minDate,
                maxDate: props.value[1] ? props.value[1] : props.maxDate,
                filter: props.timeFilter,
                isHideSecond: props.hideSecond,
            })
        } else {
            return getTimeData({
                date: (value as Array<number>)[1],
                minDate: (value as Array<number>)[0],
                maxDate: props.maxDate,
                filter: props.timeFilter,
                isHideSecond: props.hideSecond,
            })
        }
    }
}
/**
 * 获取 date 的时分秒
 * @param {timestamp} date 时间
 * @param {string} type 类型，是开始还是结束
 */
function getTimeValue(
    date: number | (number | null)[],
    type: MonthPanelTimeType,
) {
    let dateValue: Date = new Date()
    if (props.type === 'datetime') {
        dateValue = new Date(date as number)
    } else if (isArray(date)) {
        if (type === 'start') {
            dateValue = new Date(date[0] || '')
        } else {
            dateValue = new Date(date[1] || '')
        }
    }

    const hour = dateValue.getHours()
    const minute = dateValue.getMinutes()
    const second = dateValue.getSeconds()
    return props.hideSecond ? [hour, minute] : [hour, minute, second]
}

function setTime(value: number | (number | null)[], type?: MonthPanelTimeType) {
    if (
        isArray(value) &&
        value[0] &&
        value[1] &&
        type === 'start' &&
        timeType.value === 'start'
    ) {
        type = 'end'
    }
    timeType.value = type || ''
    timeValue.value = getTimeValue(value, type || '')
}
function handleDateChange({
    value,
    type,
}: {
    value: number | (number | null)[]
    type?: MonthPanelTimeType
}) {
    if (!isEqual(value, props.value)) {
        // 内部保存一个值，用于判断新老值，避免监听器触发
        innerValue.value = value
        handleChange(value)
    }
    // datetime 和 datetimerange 类型，需要计算 timeData 并做展示
    if (props.type.indexOf('time') > -1) {
        setTime(value, type)
    }
}
function handleTimeChange({ value }: { value: any[] }) {
    if (!props.value) {
        return
    }
    if (props.type === 'datetime' && isNumber(props.value)) {
        const date = new Date(props.value)
        date.setHours(value[0])
        date.setMinutes(value[1])
        date.setSeconds(props.hideSecond ? 0 : value[2])
        const dateTime = date.getTime()
        handleChange(dateTime)
    } else if (isArray(props.value) && props.type === 'datetimerange') {
        const [start, end] = props.value!
        const dataValue = timeType.value === 'start' ? start : end
        const date = new Date(dataValue || '')
        date.setHours(value[0])
        date.setMinutes(value[1])
        date.setSeconds(props.hideSecond ? 0 : value[2])
        const dateTime = date.getTime()

        if (dateTime === dataValue) return

        const finalValue = [start, end]
        if (timeType.value === 'start') {
            finalValue[0] = dateTime
        } else {
            finalValue[1] = dateTime
        }
        innerValue.value = finalValue // 内部保存一个值，用于判断新老值，避免监听器触发
        handleChange(finalValue)
    }
}
function handlePickStart() {
    emit('pickstart')
}
function handlePickEnd() {
    emit('pickend')
}

const monthScroll = (event: { detail: { scrollTop: number } }) => {
    if (months.value.length <= 1) {
        return
    }
    const scrollTop = Math.max(0, event.detail.scrollTop)
    doSetSubtitle(scrollTop)
}

/**
 * 设置小标题
 * scrollTop 滚动条位置
 */
function doSetSubtitle(scrollTop: number) {
    let height: number = 0 // 月份高度和
    for (let index = 0; index < months.value.length; index++) {
        height = height + months.value[index].height
        if (scrollTop < height) {
            scrollIndex.value = index
            return
        }
    }
}

defineExpose<MonthPanelExpose>({
    scrollIntoView,
})
</script>

<style lang="scss" scoped>
@import './index.scss';
</style>
